<template><div><!--  -->
<h2 id="什么是渲染" tabindex="-1"><a class="header-anchor" href="#什么是渲染" aria-hidden="true">#</a> 什么是渲染？</h2>
<p>  在浏览器中的渲染就是将我们的 HTML 文档转化为屏幕（浏览器视窗）上的像素信息的整个过程。而这个像素信息描述了屏幕上的每一个像素点应该显示什么颜色。</p>
<h2 id="浏览器是如何渲染页面的" tabindex="-1"><a class="header-anchor" href="#浏览器是如何渲染页面的" aria-hidden="true">#</a> 浏览器是如何渲染页面的？</h2>
<p>  当浏览器的网络线程收到 HTML 文档后，会产生一个渲染任务并将其传递给渲染主线程的消息队列。在事件循环机制的作用下，渲染主线程取出消息队列中的渲染任务，开启渲染流程。</p>
<figure><img src="/assets/studyNote/renderTheory/renderTime.png" alt="渲染时间点" tabindex="0" loading="lazy"><figcaption>渲染时间点</figcaption></figure>
<p>  整个渲染流程分为多个阶段，分别是：HTML 解析、样式计算、布局、分层、绘制、分块、光栅化，画每一个阶段都有明确的输出输入，上一个阶段的输出会成为下一个阶段的输入。这样整个渲染流程就形成了一套组织严密的生产流水线。</p>
<figure><img src="/assets/studyNote/renderTheory/renderRoute.drawio.png" alt="渲染流水线" tabindex="0" loading="lazy"><figcaption>渲染流水线</figcaption></figure>
<hr>
<h3 id="渲染的第一步是解析-html" tabindex="-1"><a class="header-anchor" href="#渲染的第一步是解析-html" aria-hidden="true">#</a> 渲染的第一步是<strong>解析 HTML</strong></h3>
<p>  解析过程中遇到 CSS 解析 CSS，遇到 JS 执行 JS。为了提高解析效率，浏览器在开始解析之前，会启动一个预解析的线程，事先下载 HTML 中的外部 CSS 文件和 JS 文件。</p>
<p>  如果在主线程遇到 link 位置，此时外部的 CSS 文件还没有下载解析好，主线程不会等待，继续解析后续的 HTML。这是因为下载和解析 CSS 的工作是在预解析线程中执行的。这就是 CSS 不会阻塞 HTML 的根本原因。</p>
<p>  如果主线程解析到 srcipt 位置，会停止解析 HTML 转而等待 JS 文件下载好并将全局代码解析执行完成后才能继续解析 HTML。这是因为 JS 代码执行的过程可能会修改当前文档的 DOM 树，所以 DOM 树的生成必须暂停。这就是 JS 会阻塞 HTML 的根本原因。</p>
<p>  第一步完成后，会得到 DOM 树和 CSSOM 树，当浏览器的默认样式、内部样式、外部样式、行内样式均包含在 CSSOM 树中。</p>
<figure><img src="/assets/studyNote/renderTheory/parseHTML.png" alt="解析HTML" tabindex="0" loading="lazy"><figcaption>解析HTML</figcaption></figure>
<p>所谓的 DOM 树和 CSSOM 树，其实就是一个对象。</p>
<h3 id="第二步是样式计算" tabindex="-1"><a class="header-anchor" href="#第二步是样式计算" aria-hidden="true">#</a> 第二步是<strong>样式计算</strong></h3>
<p>  主线程会遍历得到的 DOM 树，依次为树中的每个节点计算出它的最终样式，称之为 Computed Style。在这一过程中，很多预设值都会变成绝对值，比如 red 会变成 rgb(255,0,0)；相对单位也会变成绝对单位，比如 rpx、em 会变成 px。这一步完成后，会得到一个带有样式的 DOM 树。</p>
<h3 id="第三步是布局" tabindex="-1"><a class="header-anchor" href="#第三步是布局" aria-hidden="true">#</a> 第三步是<strong>布局</strong></h3>
<p>  布局完成后会得到布局树。
在布局阶段会依次遍历 DOM 树的每一个节点，计算每一个节点的几何信息，列如节点的宽高、相对包含块的位置。</p>
<p>  大部分的时候 DOM 树和布局树并非一一对应。比如 display:none 的节点没有几何信息，因此不会生成到布局树；又比如使用了伪元素选择器，虽然 DOM 树中不存在这些伪元素，但它们拥有几何信息，所以会生成布局树中。还有匿名块盒、匿名行盒等等都会导致 DOM 树和布局树无法一一对应。</p>
<h3 id="第四步是分层" tabindex="-1"><a class="header-anchor" href="#第四步是分层" aria-hidden="true">#</a> 第四步是<strong>分层</strong></h3>
<p>  主线程会使用一套复杂的策略对整个布局树进行分层。</p>
<p>  分层的好处在于，将来某一层改变后，仅会对该层进行后续处理。从而提升效率。</p>
<p>  滚动条、堆叠上下文、transform、opacity 等样式都会或多或少的影响分层的结果，也可以通过 will-change 属性更大程度的影响分层结果。</p>
<h3 id="第五步是绘制" tabindex="-1"><a class="header-anchor" href="#第五步是绘制" aria-hidden="true">#</a> 第五步是<strong>绘制</strong></h3>
<p>  主线程会为每个层单独产生绘制指令，用于描述这一层的内容该如何画出来。</p>
<p>  绘制完成后，主线程将每个图层的绘制信息提交给合成线程，剩余工作由合成线程完成。</p>
<p>  合成线程首先会对每个图层进行分块将其划分为更小的区域。</p>
<p>  它会从线程池中拿去多个线程来完成分块工作。</p>
<h3 id="第六步是光栅化" tabindex="-1"><a class="header-anchor" href="#第六步是光栅化" aria-hidden="true">#</a> 第六步是<strong>光栅化</strong></h3>
<p>  合成线程会将块信息提交 GPU 进程，以及高速度完成光栅化。</p>
<p>  GPU 进程会开启多个线程来完成光栅化，并有限处理靠近视口区域的块。</p>
<p>  光栅化的结果，就是一块一块的位图。</p>
<h3 id="第七步是画" tabindex="-1"><a class="header-anchor" href="#第七步是画" aria-hidden="true">#</a> 第七步是<strong>画</strong></h3>
<p>  合成线程拿到每个层、每个块的位图后，生成一个个（指引（quad））信息。</p>
<p>  指引会标识出每个位图应该画到屏幕的那个位置，以及会考虑到旋转、缩放等变形。</p>
<p>  变形会发生在合成线程，与渲染主线程无关，这就是 transform 效率高的本质原因。</p>
<p>  合成线程会把 quad 交给 GPU 进程，由 GPU 进程产生系统调用，提交给 GPU 硬件，完成最终的屏幕成像。</p>
<h2 id="什么是-reflow" tabindex="-1"><a class="header-anchor" href="#什么是-reflow" aria-hidden="true">#</a> 什么是 reflow？</h2>
<p id="p" class="mark" color="red">   <span v-pre class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mstyle mathcolor="#376956"><mrow><mi>r</mi><mi>e</mi><mi>f</mi><mi>l</mi><mi>o</mi><mi>w</mi></mrow></mstyle></mrow><annotation encoding="application/x-tex">\color{#376956}{reflow}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord" style="color:#376956;"><span class="mord mathnormal" style="color:#376956;">re</span><span class="mord mathnormal" style="margin-right:0.10764em;color:#376956;">f</span><span class="mord mathnormal" style="margin-right:0.01968em;color:#376956;">l</span><span class="mord mathnormal" style="color:#376956;">o</span><span class="mord mathnormal" style="margin-right:0.02691em;color:#376956;">w</span></span></span></span></span> 的本质就是重新计算布局树。</p>
<p>  当进行了会影响布局树的操作后，需要重新计算布局树，会引发重新布局。</p>
<p>  为了避免连续多次操作导致布局树反复计算，浏览器会合并这些操作，当 JS 代码全部执行完成后在进行统一计算。所以，改动属性造成的 <mark>reflow</mark> 是异步完成的。</p>
<p>  也同样因为如此，当 JS 获取布局属性时，就可能造成无法获取到最新的布局信息。</p>
<p>  浏览器在反复权衡下，最终决定获取属性立即 <mark>reflow</mark> 。</p>
<h2 id="什么是-repaint" tabindex="-1"><a class="header-anchor" href="#什么是-repaint" aria-hidden="true">#</a> 什么是 repaint？</h2>
<p>   <mark>repaint</mark> 的本质就是重新根据分层信息计算了分层指令。</p>
<p>  当改动了可见样式后，就需要重新计算，会引发 <mark>repaint</mark> 。</p>
<p>  由于元素的布局信息也属于可见样式所以 reflow 一定会引起 <mark>repaint</mark> 。</p>
<h2 id="为什么-transform-的效率高" tabindex="-1"><a class="header-anchor" href="#为什么-transform-的效率高" aria-hidden="true">#</a> 为什么 transform 的效率高？</h2>
<p>  应为 transform 既不会影响布局也不会影响绘制指令它影响的只是渲染流程的最后一个画的阶段。由于画的阶段在合成线程中，所以 transform 的变化几乎不会影响渲染主线程。反之，主线程无论如何繁忙，也不会影响 transform 的变化。</p>
</div></template>


